package org.rainbowinverted.charging.device.connect.ykc.server.handler;

import org.rainbowinverted.charging.device.api.operation.dto.resp.DeviceRespEventDTO;
import org.rainbowinverted.charging.device.api.operation.dto.resp.EventInfo;
import org.rainbowinverted.charging.device.connect.ykc.server.attribute.DeviceInfo;
import org.rainbowinverted.charging.device.connect.ykc.server.constants.ChannelAttributeKeyConstants;
import org.rainbowinverted.charging.device.connect.ykc.server.constants.YkcDeviceGunHomeStatusEnum;
import org.rainbowinverted.charging.device.connect.ykc.server.constants.YkcDeviceGunStatusCacheKeyConstants;
import org.rainbowinverted.charging.device.connect.ykc.server.constants.YkcDeviceGunStatusEnum;
import org.rainbowinverted.charging.device.connect.ykc.server.message.in.*;
import org.rainbowinverted.charging.device.connect.ykc.server.message.out.YkcDeviceStatusInfoOut;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

/**
 * 设备枪状态信息处理器
 * @author hehuaiping
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class YkcDeviceGunStatusHandler extends YkcAbstractHandler<YkcDeviceStatusInfoIn, YkcDeviceStatusInfoOut> {
    private final BoundHashOperations<String, String, YkcDeviceStatusReportIn> boundedHashOps;

    public YkcDeviceGunStatusHandler(RedisTemplate redisTemplate) {
        this.boundedHashOps = redisTemplate.boundHashOps(YkcDeviceGunStatusCacheKeyConstants.YKC_DEVICE_GUN_STATUS_CACHE_KEY);
    }

    @Override
    public void read(ChannelHandlerContext channel, YkcMessageIn message) {
        ByteBuf messageBody = message.getMessageBody();
        // 订单编号
        String orderNo = readOrderNo(messageBody);
        Long deviceNo = readDeviceNo(messageBody);
        Integer gunNo = readGunNo(messageBody);
        // 充电枪信息
        String gunStatus = parseGunStatus(messageBody.readByte());
        String gunHomeStatus = parseGunHomeStatus(messageBody.readByte());
        Boolean gunInserted = messageBody.readByte() == 0x01;
        // 充电信息1位小数
        Integer voltage = messageBody.readShortLE() * 10;
        Integer current = messageBody.readShortLE() * 10;
        // 偏移量-50
        Integer gunLineTemp = ((Byte)messageBody.readByte()).intValue();
        Long gunLineNo = messageBody.readLongLE();
        // 电池信息
        Integer soc = ((Byte)messageBody.readByte()).intValue();
        Integer batteryGroupMaxTemp = ((Byte)messageBody.readByte()).intValue();
        // 计量信息
        Integer chargeTime = ((Short)messageBody.readShortLE()).intValue();
        Integer remainingTime = ((Short)messageBody.readShortLE()).intValue();
        Integer chargeElectricity = messageBody.readIntLE();
        Integer lossChargeElectricity = messageBody.readIntLE();
        Integer chargeAmount = messageBody.readIntLE();
        YkcDeviceStatusInfoIn ykcDeviceStatusInfoIn = YkcDeviceStatusInfoIn.of(orderNo, deviceNo, gunNo, gunStatus,
                gunHomeStatus, gunInserted, voltage, current, gunLineTemp,
                gunLineNo, soc, batteryGroupMaxTemp, chargeTime, remainingTime,
                chargeElectricity, lossChargeElectricity, chargeAmount);
        Boolean emergencyStatus = hasEmergencyStatus(gunStatus);
        // 状态上报
        statusReport(ykcDeviceStatusInfoIn, emergencyStatus);
        DeviceInfo deviceInfo = channel.channel().attr(ChannelAttributeKeyConstants.CHANNEL_ATTR_DEVICE_INFO).get();
        deviceInfo.setEmergency(emergencyStatus);
        complete(message.getFrameSerialNo(), ykcDeviceStatusInfoIn);
        log.info("device status info: {}", ykcDeviceStatusInfoIn);
    }

    @Override
    public void write(ByteBuf byteBuf, YkcDeviceStatusInfoOut ykcDeviceStatusInfoOut) {
        writeDeviceNo(byteBuf, ykcDeviceStatusInfoOut.getDeviceNo());
        writeGunNo(byteBuf, ykcDeviceStatusInfoOut.getGunNo());
        createCompletableFuture(ykcDeviceStatusInfoOut.getFrameSerialNo());
    }

    private void statusReport(YkcDeviceStatusInfoIn ykcDeviceStatusInfoIn, Boolean emergencyStatus) {
        // 判断是都是插拔枪事件
        if (YkcDeviceGunStatusEnum.IDLE.getStatus().equals(ykcDeviceStatusInfoIn.getGunStatus())) {
            EventInfo eventInfo = EventInfo.buildDefault();
            eventInfo.setGunInserted(ykcDeviceStatusInfoIn.getGunInserted());
            eventInfo.setGunPullOfOut(!ykcDeviceStatusInfoIn.getGunInserted());
           /* deviceEventComponent.broadcastEventReport(DeviceRespEventDTO.of(ykcDeviceStatusInfoIn.getDeviceNo(),
                    ykcDeviceStatusInfoIn.getGunNo(), LocalDateTime.now(), eventInfo, LocalDateTime.now()));*/
        }
        // 充电枪信息
        YkcDeviceGunInfoIn gunInfo = YkcDeviceGunInfoIn.of(ykcDeviceStatusInfoIn.getGunNo(),
                parseChargeGunStatus(ykcDeviceStatusInfoIn.getGunStatus(), ykcDeviceStatusInfoIn.getGunInserted()));
        // 功率 = 电压 * 电流
        YkcDeviceGunChargeInfoIn chargeGunChargeInfo = YkcDeviceGunChargeInfoIn.of(ykcDeviceStatusInfoIn.getGunNo(),
                ykcDeviceStatusInfoIn.getOrderNo(),
                // 电压两位小数 + 电流两位小数，结果去掉2位小数
                (ykcDeviceStatusInfoIn.getVoltage() * ykcDeviceStatusInfoIn.getCurrent()) / 100,
                ykcDeviceStatusInfoIn.getCurrent(), ykcDeviceStatusInfoIn.getChargeElectricity(),
                ykcDeviceStatusInfoIn.getChargeTime().longValue());
        // 设备信息
        YkcDeviceStatusReportIn statusInfoDTO = YkcDeviceStatusReportIn.of(ykcDeviceStatusInfoIn.getDeviceNo(),
                gunInfo, chargeGunChargeInfo, ykcDeviceStatusInfoIn.getVoltage(),
                0, 0, emergencyStatus, LocalDateTime.now());
        cacheGunStatus(statusInfoDTO);
        // deviceEventComponent.broadcastDeviceStatusInfoReport(statusInfoDTO);
    }

    public YkcDeviceStatusReportIn getGunStatus(Long deviceNo, Integer gunNo) {
        String gunCacheKey = getDeviceGunCacheKey(deviceNo, gunNo);
        return boundedHashOps.get(gunCacheKey);
    }

    /**
     * 缓存充电枪状态信息
     * @param statusInfoDTO 充电枪状态信息
     */
    private void cacheGunStatus(YkcDeviceStatusReportIn statusInfoDTO) {
        String gunCacheKey = getDeviceGunCacheKey(statusInfoDTO.getDeviceNo(), statusInfoDTO.getGunChargeInfo().getGunNo());
        boundedHashOps.expire(5, TimeUnit.MINUTES);
        boundedHashOps.put(gunCacheKey, statusInfoDTO);
    }

    private String getDeviceGunCacheKey(Long deviceNo, Integer gunNo) {
        return YkcDeviceGunStatusCacheKeyConstants.YKC_DEVICE_GUN_STATUS_CACHE_KEY + ":" +deviceNo + "_" + gunNo;
    }

    public YkcDeviceGunStatusIn parseChargeGunStatus(String gunStatus, Boolean gunInserted) {
        YkcDeviceGunStatusIn chargeGunStatus = YkcDeviceGunStatusIn.buildDefault();
        switch (YkcDeviceGunStatusEnum.valueOf(gunStatus)) {
            case IDLE: {
                chargeGunStatus.setIdling(Boolean.TRUE);
                break;
            }
            case CHARGING: {
                chargeGunStatus.setCharge(Boolean.TRUE);
                break;
            }
            case EMERGENCY: {
                log.error("gun emergency");
                break;
            }
            default:
                log.error("unknown gun status");
        }
        chargeGunStatus.setInserted(gunInserted);
        return chargeGunStatus;
    }

    private String parseGunStatus(Byte gunStatusByte) {
        return switch (gunStatusByte) {
            case 0x00 -> YkcDeviceGunStatusEnum.OFFLINE.getStatus();
            case 0x01 -> YkcDeviceGunStatusEnum.EMERGENCY.getStatus();
            case 0x02 -> YkcDeviceGunStatusEnum.IDLE.getStatus();
            case 0x03 -> YkcDeviceGunStatusEnum.CHARGING.getStatus();
            default -> YkcDeviceGunStatusEnum.ERROR.getStatus();
        };
    }

    private String parseGunHomeStatus(Byte gunHomeStatusByte) {
        return switch (gunHomeStatusByte) {
            case 0x00 -> YkcDeviceGunHomeStatusEnum.NOT_HOME.getStatus();
            case 0x01 -> YkcDeviceGunHomeStatusEnum.HOMING.getStatus();
            default -> YkcDeviceGunHomeStatusEnum.UNKNOWN.getStatus();
        };
    }

    private Boolean hasEmergencyStatus(String gunStatusReport) {
        return YkcDeviceGunStatusEnum.EMERGENCY.getStatus().equals(gunStatusReport);
    }

}
